Skip to main content
Version: Next

iOS

HumaModuleKit is a library that contains an engine for building Modules. Note that it doesn't include any Module implementations.

Getting Started

How to create a module

  1. First of all, you need to create a module. Simply put, a Module describes a task that can be performed by a user, usually for an unlimited number of times.

    A Module has three main responsibilities:

    • render itself on a Module Card (required);
    • display an Input flow (optional);
    • display a Detail flow (optional).

    A Module can also display any other flows.

  2. To create a module, ultimately, you have to conform to protocol AnyModule. If your module supports Detail flow, you must conform to protocol AnyModuleWithDetail.

    The framework also includes default abstract classes to make your life easier:

    • class Module. This class implements most of the logic for rendering a Module on a Module Card and building the default Detail page. All you have to do is override a few methods.
    • class ModuleWithDefaultInput: Module. This class extends class Module by adding additional logic to display the default Input page.

    The HumaModules framework contains several default module implementations. Here's a snippet from class WaistModule, which you can use as an example for creating your modules:

import HumaModuleKit

/// Allows users to submit waist readings and view previous data.
public final class WaistModule: ModuleWithDefaultInput<DoublePrimitives.Waist.InputTransformer> {

/// Title of a module to display on List and Detail if `config` doesn't contain a name.
public override class var defaultDisplayName: String { Strings.moduleWaistTitle }
/// Name of a module to display on analytics tracking.
public override class var analyticsName: String { "Waist" }

/// Is used to populate the default detail screen.
public override var detailConfig: DetailConfig {
makeDetailConfig(sections: makeDetailSections(), actions: [makeDetailAction()])
}

/// Is used to populate the input view.
public override var inputViewModel: ModuleInputViewModel {
makeInputViewModel(
subheading: Strings.moduleWaistInputSubtitle,
unit: unit.displayName,
inputType: .floatNumber,
validator: Self.validateInput
)
}

/// Used to create the renderer for Module List.
/// - Parameter config: External configuration for the renderer.
/// - Returns: Created a standard renderer for displaying result count, and, optionally, a chart preview.
public override func makeStandardRenderer(config: ModuleRenderingMode.StandardConfig) -> AnyModuleStandardRenderer {
makeStandardRenderer(
primitiveType: Primitive.self,
config: config,
valueFormatter: .default(unit: unit, range: .zero),
chartConverter: .line
)
}
}

How to display modules

Now that you have implemented the required modules, it's time to try them in action 🚀.

  1. Start by creating an enumeration with identifiers for all required modules. Your enum should have a raw value type of String and conform to ModuleIdentifiable. The raw values must match the module identifiers in your Configuration.
import HumaModuleKit

/// Enumerates all modules supported by the Sample App.
enum ModuleID: String, ModuleIdentifiable {

// Declare all module identifiers here

case waist = "Waist"
case temperature = "Temperature"
case questionnaire = "Questionnaire"

// Return types of the module classes here
func moduleType(for config: GroupedModuleConfig) -> AnyModule.Type {
switch self {
case .waist:
return WaistModule.self
case .temperature:
return TemperatureModule.self
case .questionnaire:
return QuestionnaireModule.self
}
}

func supportsGrouping(for config: ModuleConfig) -> Bool {
self == .questionnaire
}
}
  1. Implement protocol AnyModuleTypeRepository to let the framework know about the modules and their identifiers. Register this and other dependencies in your app's container:
struct DependencyContainer {
var resolver: Resolver {
let repository = ModuleTypeRepository()
return Container()
.register(AnyModuleTypeRepository.self, instance: repository)
}
}
}

You can find the complete list of dependencies on the Installing tab.

Hint. A module can resolve any dependency, including module-specific ones. So if your module requires custom dependencies, don't forget to register them.

  1. Instantiate class ModulesCoordinator and attach it to a parent coordinator. That's all. Enjoy your modules!
final class TabBarCoordinator: BaseCoordinator {

let container = DependencyContainer()

override func start(animated: Bool) -> Completable? {
fatalError("TODO")
}

private func openModulesTab() {
let child = ModulesCoordinator(navigator: navigator, resolver: container.resolver, listOptions: .init())
addChild(child)
}
}

Documentation

API Reference